package com.hotel.BL;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;





import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.EntityName;
import com.Util;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.CompositeFilterOperator;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.FilterPredicate;

import data.Charge;
import data.Package;
import data.PackageByDate;
import data.Reservation;
import data.ReservationStatus;
import data.RoomType;
import data.SubReservation;

public class ChargeLogic extends AbstractBL {

	public ChargeLogic(HttpServletRequest req,HttpServletResponse resp)
	{
		this.req = req;
		this.resp = resp;
	}

	
	public Charge GetCharge(long id) {
		Key key = KeyFactory.createKey(EntityName.Charge, id);
		try {
			Entity lang = datastore.get(key);
			Charge c = new Charge();
			c.setEntity(lang);
			return c;
		} catch (EntityNotFoundException e) {
			return null;
		}
	}

	public List<Charge> GetCharge(long hotelId,Date date)
	{
		Query q = new Query(EntityName.Charge);
		
		Filter filterHotel = new FilterPredicate("HotelId",FilterOperator.EQUAL,hotelId);
		Filter filterDate = new FilterPredicate("chargeDate",FilterOperator.EQUAL,date);

		Filter filter = CompositeFilterOperator.and(filterHotel, filterDate);
		q.setFilter(filter);
		
		return GetList(datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()));
	}
	
	public List<Charge> GetAllCharge(long hotelId) {
		Query q = new Query(EntityName.Charge);

		Filter filterHotel = new FilterPredicate("HotelId",
				FilterOperator.EQUAL, hotelId);

		q.setFilter(filterHotel);

		return GetList(datastore.prepare(q).asList(
				FetchOptions.Builder.withDefaults()));
	}
		
	public Charge GetCharge(Entity entity) {
		Charge item = new Charge();
		item.setEntity(entity);
		return item;
	}

	public List<Charge> GetList(List<Entity> entities) {
		List<Charge> list = new ArrayList<Charge>();
		if(entities!=null)
		for (Entity e : entities) {
			Charge a = new Charge();
			a.setEntity(e);
			list.add(a);
		}
		return list;
	}

	public Charge AddCharge(long hotelId, String description,long chargeQty,
			double chargeAmount, Date chargeDate, double discount,
			String subFolio, double tax, boolean isLock,boolean isRoomCharge,boolean isTemp,long reasonString
			,String email ,String chargeType)
			throws EntityNotFoundException {
		//TaxLogic tLogic = new TaxLogic();
		Charge c = new Charge();
		c.setHotelId(hotelId);
		c.setDescription(description);
		c.setChargeAmount(chargeAmount);
		c.setChargeDate(chargeDate);
		c.setDiscount(discount);
		c.setQty(chargeQty);
		//TODO: workaround from tee
		//c.setTax(tLogic.CalTax(hotelId, chargeAmount));
		c.setTax(tax);
		c.setSubFolio(subFolio);
		c.setLock(isLock);
		c.setRoomCharge(isRoomCharge);
		c.setRefund(false);
		c.setTemp(isTemp);
		c.setReasondetailId(reasonString);
		c.setEmailMassage(email);
		c.setChargeType(chargeType);
		Key k = datastore.put(c.getEntity());
		c.setId(k.getId());
		c.setKey(k);
		return c;
	}
	
	public Charge EditRefund(long charegeId) throws EntityNotFoundException{
		Charge c = GetCharge(charegeId);
		c.setRefund(true);
		Key k = datastore.put(c.getEntity());
		c.setId(k.getId());
		c.setKey(k);
		return c;
		}
	
	public Charge EditChargeAmount(long charegeId,double amount,double discount,double tax) throws EntityNotFoundException{
		Charge c = GetCharge(charegeId);
		c.setChargeAmount(amount);
		c.setDiscount(discount);
		c.setTax(tax);
		Key k = datastore.put(c.getEntity());
		c.setId(k.getId());
		c.setKey(k);
		return c;
		}
	
	public Charge EditChargeAmountWithDiscount(long charegeId,double amount,double tax) throws EntityNotFoundException{
		Charge c = GetCharge(charegeId);
		double charge = c.getChargeAmount();
		double dc = c.getDiscount();
		
		c.setChargeAmount(amount);
		c.setTax(tax);
		c.setDiscount(charge-amount+dc);
		Key k = datastore.put(c.getEntity());
		c.setId(k.getId());
		c.setKey(k);
		return c;
		}
	
	public Charge EditFolio(long charegeId, long folioId, String subFolioId) throws EntityNotFoundException{
		Charge c = GetCharge(charegeId);
		c.setFolioId(folioId);
		c.setSubFolio(subFolioId);
		Key k = datastore.put(c.getEntity());
		c.setId(k.getId());
		c.setKey(k);
		return c;
	}
	
	public Charge EditDiscount(long charegeId,double amount) throws EntityNotFoundException{
		Charge c = GetCharge(charegeId);
		c.setDiscount(c.getDiscount() + amount);
		Key k = datastore.put(c.getEntity());
		c.setId(k.getId());
		c.setKey(k);
		return c;
		}
	
	public boolean ExceptTex(String charegeId) throws EntityNotFoundException{
		boolean issucc = true;
		if(charegeId != null && !charegeId.equals(""))
		{
			String[] chargeList = charegeId.trim().split(",");
			for(int i = 0;i<chargeList.length;i++){
			Charge g = GetCharge(Long.parseLong(chargeList[i]));
					
			    g.setTax(0.0);
			    
				Key kg = datastore.put(g.getEntity());
				g.setId(kg.getId());
				g.setKey(kg);
			}
		}
		else
		{
			issucc = false;
		}
	
		return issucc;
		}
	
	public List<Charge> CalCharge(long hotelId,List<SubReservation> sublist,List<PackageByDate> packagelist,
			Date mindate_old, Date maxdate_old) throws EntityNotFoundException, ParseException{
		RoomTypeLogic roomtypeLogic = new RoomTypeLogic(req,resp);
		PackageLogic packageLogic = new PackageLogic(req,resp);
		TaxLogic taxLogic = new TaxLogic(req,resp);
		
		Reservation res = sublist.get(0).getParent();
		List<Charge> chargelist = res.getChargeList();
		
		Date[] minmaxDate = findMinMaxResDate(sublist);
		Date mindate = minmaxDate[0];
		Date maxdate = minmaxDate[1];
		int span = Util.daysBetween(mindate, maxdate);
		double taxPerReservation = taxLogic.GetTaxPerReservation(hotelId);
		
		Collections.sort(chargelist, new Comparator<Charge>(){
			public int compare(Charge c1, Charge c2) {
				return (int)(c1.getChargeDate().getTime() - c2.getChargeDate().getTime());
			}
		});
				
		for(int x=0;x<span;x++){
			Date calDate = Util.addDays(mindate, x);
			
			RoomType roomtype = roomtypeLogic.GetRoomTypeByDate(sublist, calDate);
			Package packages = packageLogic.GetPackageByDate(packagelist, calDate);
			Double price = CalPrice(roomtype,packages,res);
			
			//get charge id
			boolean getcharge = false;
			long chargeid = 0;
			Charge chargeByIndex = null;
			for(int i = 0;i<chargelist.size();i++){
				if(chargelist.get(i).getChargeDate().equals(Util.addDays(mindate, x))){
					chargeid = chargelist.get(i).getId();
					chargeByIndex = chargelist.get(i);
					getcharge = true;
					break;
				}
			}
			if(getcharge){
				HashMap<String,Double> calTax = taxLogic.CalRoomTax(hotelId,price,x);
				double amount = chargeByIndex.getDiscount() + chargeByIndex.getChargeAmount();
				if(chargeByIndex.equals(chargelist.get(0))) amount -= taxPerReservation;
				double discountPerc = chargeByIndex.getDiscount() / amount;
				double chargeAmount = calTax.get("costIncludeTax") * (1-discountPerc);
				double discount = calTax.get("costIncludeTax") * discountPerc;
				if(x==0){
					chargeAmount = ((calTax.get("costIncludeTax")-taxPerReservation) * (1-discountPerc))+taxPerReservation;
					discount = (calTax.get("costIncludeTax")-taxPerReservation) * discountPerc;
				}
				calTax = taxLogic.CalRoomTax(hotelId,chargeAmount,x,true);
				EditChargeAmount(chargeid, chargeAmount,discount,calTax.get("tax"));
				chargeByIndex.setChargeAmount(chargeAmount);
				chargeByIndex.setDiscount(discount);
				chargeByIndex.setTax(calTax.get("tax"));
			}else{
				HashMap<String,Double> calTax = taxLogic.CalRoomTax(hotelId,price,x);
				Date chargeDate = Util.addDays(mindate, x);
				double discountPerc = 0;
				if(chargelist.size()>0)
					if(chargeDate.before(chargelist.get(0).getChargeDate()))
						discountPerc = chargelist.get(0).getDiscount() / (chargelist.get(0).getDiscount() + chargelist.get(0).getChargeAmount() - taxPerReservation);
					else if(chargeDate.after(chargelist.get(chargelist.size()-1).getChargeDate()))
						{
							Charge lastIndex = chargelist.get(chargelist.size()-1);
							discountPerc = lastIndex.getDiscount() / (lastIndex.getDiscount() + lastIndex.getChargeAmount());
						}
				double chargeAmount = calTax.get("costIncludeTax") * (1-discountPerc);
				double discount = calTax.get("costIncludeTax") * discountPerc;
				if(chargeDate.equals(res.getDate())){
					chargeAmount = ((calTax.get("costIncludeTax")-taxPerReservation) * (1-discountPerc))+taxPerReservation;
					discount = (calTax.get("costIncludeTax")-taxPerReservation) * discountPerc;
				}
				calTax = taxLogic.CalRoomTax(hotelId,chargeAmount,x,true);
				Charge aa = AddCharge(hotelId, "Room Rate",0, calTax.get("costIncludeTax"), chargeDate,discount,
						"",calTax.get("tax"), false, true, true, 0 ,null ,null);
				chargelist.add(aa);
			}
		}
		if(mindate_old.before(mindate)){
			for(int x=0;x<chargelist.size();x++){
				if(chargelist.get(x).getChargeDate().before(mindate)){
					DeleteCharge(chargelist.get(x).getId());
					chargelist.remove(x);
					x--;
				}
			}
		}
		if(maxdate_old.after(Util.addDays(maxdate, -1))){
			for(int x=0;x<chargelist.size();x++){
				if(chargelist.get(x).getChargeDate().after(Util.addDays(maxdate, -1)) ){
					DeleteCharge(chargelist.get(x).getId());
					chargelist.remove(x);
					x--;
				}
			}
		}
		String chargeliststring = "";
		//double totalPrice = 0;
		for(int i=0;i<chargelist.size();i++){
			chargeliststring += chargelist.get(i).getId()+",";
			//totalPrice += chargelist.get(i).getChargeAmount();
		}
		chargeliststring = chargeliststring.substring(0, chargeliststring.length()-1);
		new ReservationLogic(req,resp).EditChargeListString(res.getId(), chargeliststring);
		
		return chargelist;
	}
	
	private Date[] findMinMaxResDate(List<SubReservation> sublist)
	{
		Date mindate = sublist.get(0).getDate();
		Date maxdate = sublist.get(0).getDateTo();
		for(int i=0;i<sublist.size();i++){
			if(sublist.get(i).getDate().before(mindate)){
				mindate = sublist.get(i).getDate();
			}
			if(sublist.get(i).getDateTo().after(maxdate)){
				maxdate = sublist.get(i).getDateTo();
			}
		}
		return new Date[] {mindate,maxdate};
	}
	
	public Double CalPrice(RoomType roomtype,Package packages,Reservation resv){
		PackagePriceLogic packagePriceLogic = new PackagePriceLogic(req,resp);
		List<Double> price = packagePriceLogic.GetPackagePrice(roomtype,packages);
		double pricepernight = price.get(0);
		double extrabedprice = price.get(1);
		double upchargeperson =  price.get(2);
		long occ = resv.getAdult() + resv.getChild();
		long extrabed = resv.getExtrabed();
		long stdOcc = roomtype.getStandardOccupancy();
		long upchargeocc = occ-(stdOcc+extrabed);
		upchargeocc = (upchargeocc<0)? 0:upchargeocc;

		return pricepernight+ (extrabedprice*extrabed)+(upchargeperson*upchargeocc);
	}
	
	public Double CalPrice(RoomType roomtype,Package packages,long occ,long extrabed){
		PackagePriceLogic packagePriceLogic = new PackagePriceLogic(req,resp);
		List<Double> price = packagePriceLogic.GetPackagePrice(roomtype,packages);
		double pricepernight = price.get(0);
		double extrabedprice = price.get(1);
		double upchargeperson =  price.get(2);
		long stdOcc = roomtype.getStandardOccupancy();
		long upchargeocc = occ-(stdOcc+extrabed);
		upchargeocc = (upchargeocc<0)? 0:upchargeocc;

		return pricepernight+ (extrabedprice*extrabed)+(upchargeperson*upchargeocc);
	}
	
	public void DeleteCharge(long chargeId){
		Key key = KeyFactory.createKey(EntityName.Charge, chargeId);
		datastore.delete(key);
	}
	
	public boolean CheckChargeInUse(String listofCharge,String type)
	{
		if(listofCharge == null||!listofCharge.equals(""))
		return false;
		String [] chargeArray = listofCharge.trim().split(",");
		for(int i =0;i<chargeArray.length;i++)
		{
			Charge c = GetCharge(Long.parseLong(chargeArray[i]));
			if(c.getChargeType()==null)
			{
				continue;
			}
		if(c.getChargeType().equals(type)&&c.isRefund()){return true;}
		}	
		return false;
	}
	
	public boolean ChangeResStatus(long hotelId,String resId,String resStatus, boolean Assign, boolean isSellRoom) throws EntityNotFoundException
	{
		boolean Status = false;
		Reservation r = new ReservationLogic(req,resp).GetReservation(Long.parseLong(resId));
		ReservationStatus f = new GenericLogic(req,resp).GetReservationStatus(hotelId,
				resStatus);
		r.setReservationStatusString(String.valueOf(f.getId()));
		r.setAssign(Assign);
		r.setSellRoom(isSellRoom);
		
		Key kg = datastore.put(r.getEntity());
		r.setId(kg.getId());
		r.setKey(kg);
		if(r != null)
		{
			Status=true;
		}
		
		return Status;
	}

	public Charge UpdateCharge(Charge c)
	{
		Key key = datastore.put(c.getEntity());
		c.setId(key.getId());
		c.setKey(key);
		return c;
	}
	
	public List<Charge> GetNoShowToDay(long hotelId)
	{
		Date date = Util.GetOnlyDate(new GenericLogic(req,resp).ServerDateFromNightAudit(hotelId));
		Query q = new Query(EntityName.Charge);
		
		Filter filterHotel = new FilterPredicate("HotelId",FilterOperator.EQUAL,hotelId);
		Filter filterDate = new FilterPredicate("chargeDate",FilterOperator.EQUAL,date);
		Filter filterType = new FilterPredicate("chargeType",FilterOperator.EQUAL,"Noshow");

		Filter filter = CompositeFilterOperator.and(filterHotel, filterDate,filterType);
		q.setFilter(filter);
		
		return GetList(datastore.prepare(q).asList(
				FetchOptions.Builder.withDefaults()));
	}
	
	public List<Charge> GetEarlyCheckOutToDay(long hotelId)
	{
		Date date = Util.GetOnlyDate(new GenericLogic(req,resp).ServerDateFromNightAudit(hotelId));
		Query q = new Query(EntityName.Charge);
		
		Filter filterHotel = new FilterPredicate("HotelId",FilterOperator.EQUAL,hotelId);
		Filter filterDate = new FilterPredicate("chargeDate",FilterOperator.EQUAL,date);
		Filter filterType = new FilterPredicate("chargeType",FilterOperator.EQUAL,"EarlyCheckout");

		Filter filter = CompositeFilterOperator.and(filterHotel, filterDate,filterType);
		q.setFilter(filter);
		
		return GetList(datastore.prepare(q).asList(
				FetchOptions.Builder.withDefaults()));
	}
	
	public double GetRoomChargeRevenue(long hotelId){
		List<Charge> allroomchargetoday = GetRoomChargeToDay(hotelId);
		double cost = 0;
			for(int i=0;i< allroomchargetoday.size();i++){
				cost += allroomchargetoday.get(i).getChargeAmount();
			}
		return cost;
	}
	
	public List<Charge> GetRoomChargeToDay(long hotelId)
	{
		Date date = Util.GetOnlyDate(new GenericLogic(req,resp).ServerDateFromNightAudit(hotelId));
		Query q = new Query(EntityName.Charge);
		
		Filter filterHotel = new FilterPredicate("HotelId",FilterOperator.EQUAL,hotelId);
		Filter filterDate = new FilterPredicate("chargeDate",FilterOperator.EQUAL,date);
		Filter filterType = new FilterPredicate("isRoomCharge",FilterOperator.EQUAL,true);

		Filter filter = CompositeFilterOperator.and(filterHotel, filterDate,filterType);
		q.setFilter(filter);
		
		return GetList(datastore.prepare(q).asList(
				FetchOptions.Builder.withDefaults()));
	}
	
	public double GetRevenue(List<Charge> list){
		
		double cost = 0;
			for(int i=0;i< list.size();i++){
				cost += list.get(i).getChargeAmount();
			}
		return cost;
	}
	
	public double GetRevenue(long hotelId){
		List<Charge> list = GetCancelChargeToDay(hotelId);
		double cost = 0;
			for(int i=0;i< list.size();i++){
				cost += list.get(i).getChargeAmount();
			}
		return cost;
	}

	
	public List<Charge> GetCancelChargeToDay(long hotelId)
	{
		Date date = Util.GetOnlyDate(new GenericLogic(req,resp).ServerDateFromNightAudit(hotelId));
		Query q = new Query(EntityName.Charge);
		
		Filter filterHotel = new FilterPredicate("HotelId",FilterOperator.EQUAL,hotelId);
		Filter filterDate = new FilterPredicate("chargeDate",FilterOperator.EQUAL,date);
		Filter filterType = new FilterPredicate("chargeType",FilterOperator.EQUAL,"CancelRes");

		Filter filter = CompositeFilterOperator.and(filterHotel, filterDate,filterType);
		q.setFilter(filter);
		
		return GetList(datastore.prepare(q).asList(
				FetchOptions.Builder.withDefaults()));
	}
}


